/*
 * To change this license header, choose License Headers in Project Properties.
 * To change this template file, choose Tools | Templates
 * and open the template in the editor.
 */
package name.dougmcneil.plsql.ui;

import java.awt.Component;
import java.awt.event.ActionEvent;
import java.awt.event.ActionListener;
import java.awt.event.MouseEvent;
import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.File;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.Serializable;
import static java.lang.Boolean.FALSE;
import static java.lang.Boolean.TRUE;
import java.util.ArrayList;
import java.util.List;
import java.util.prefs.BackingStoreException;
import java.util.prefs.Preferences;
import javax.swing.ComboBoxModel;
import javax.swing.DefaultCellEditor;
import javax.swing.DefaultComboBoxModel;
import javax.swing.JComboBox;
import javax.swing.JTable;
import javax.swing.table.DefaultTableModel;
import javax.swing.table.TableColumn;
import javax.swing.table.TableModel;
import name.dougmcneil.plsql.ui.PLSQLParametersTopComponent.ParameterCellEditor.TableComboBox;
import org.openide.util.Exceptions;
import org.openide.windows.TopComponent;
import org.openide.util.NbBundle.Messages;
import org.openide.util.NbPreferences;
import org.openide.windows.Mode;
import static org.openide.windows.TopComponent.PROP_CLOSING_DISABLED;
import static org.openide.windows.TopComponent.PROP_DRAGGING_DISABLED;
import static org.openide.windows.TopComponent.PROP_KEEP_PREFERRED_SIZE_WHEN_SLIDED_IN;
import static org.openide.windows.TopComponent.PROP_MAXIMIZATION_DISABLED;
import static org.openide.windows.TopComponent.PROP_SLIDING_DISABLED;
import static org.openide.windows.TopComponent.PROP_UNDOCKING_DISABLED;
import org.openide.windows.WindowManager;

/**
 * Top component which displays something.
 */
@TopComponent.Description(
        preferredID = "PLSQLParametersTopComponent",
        iconBase = "name/dougmcneil/plsql/resources/plsql.png",
        persistenceType = TopComponent.PERSISTENCE_NEVER
)
@TopComponent.Registration(mode = "explorer", openAtStartup = false)
//@ActionID(category = "Window", id = "name.dougmcneil.plsql.ui.PLSQLParametersTopComponent")
//@ActionReference(path = "Menu/Window" /*, position = 333 */)
//@TopComponent.OpenActionRegistration(
//        displayName = "#CTL_PLSQLParametersAction",
//        preferredID = "PLSQLParametersTopComponent"
//)
@Messages({
    "CTL_PLSQLParametersAction=PLSQLParameters",
    "# {0} - title",
    "CTL_PLSQLParametersTopComponent=PL/SQL Parameters {0}",
    "HINT_PLSQLParametersTopComponent=Persisted substitution parameters for PL/SQL execution.",
    "LBL_Name_Column=Parameter",
    "LBL_VALUE_Column=Value"
})
public final class PLSQLParametersTopComponent extends TopComponent {
    
    private String _title;

    public PLSQLParametersTopComponent() {
        initComponents();
    }
    
    public PLSQLParametersTopComponent applyConfiguration(String title) {
        _title = title;
        ((ParameterTableModel) jTableParameters.getModel()).init(Data.DEFAULT.fromPerferences(title));
        setName(Bundle.CTL_PLSQLParametersTopComponent(truncateTitle(_title)));
        setToolTipText(Bundle.HINT_PLSQLParametersTopComponent());
        return applyWindowConfiguration();
    }
    
    public PLSQLParametersTopComponent applyWindowConfiguration() {
        WindowManager mgr = WindowManager.getDefault();
        Mode mode = mgr.findMode("rightSlidingSide");
        mode.dockInto(this);
        putClientProperty(PROP_CLOSING_DISABLED, TRUE);
        putClientProperty(PROP_DRAGGING_DISABLED, FALSE);
        putClientProperty(PROP_MAXIMIZATION_DISABLED, TRUE);
        putClientProperty(PROP_UNDOCKING_DISABLED, TRUE);
        putClientProperty(PROP_KEEP_PREFERRED_SIZE_WHEN_SLIDED_IN, TRUE);
        putClientProperty(PROP_SLIDING_DISABLED, TRUE);
        return this;
    }

    /**
     * This method is called from within the constructor to initialize the form. WARNING: Do NOT
     * modify this code. The content of this method is always regenerated by the Form Editor.
     */
    // <editor-fold defaultstate="collapsed" desc="Generated Code">//GEN-BEGIN:initComponents
    private void initComponents() {

        jScrollPane1 = new javax.swing.JScrollPane();
        jTableParameters = new TooltipTable();

        jTableParameters.setModel(getTableModel());
        jTableParameters.setDoubleBuffered(true);
        jTableParameters.setRowHeight(20);
        setCellEditors();
        jScrollPane1.setViewportView(jTableParameters);

        javax.swing.GroupLayout layout = new javax.swing.GroupLayout(this);
        this.setLayout(layout);
        layout.setHorizontalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.PREFERRED_SIZE, 245, javax.swing.GroupLayout.PREFERRED_SIZE)
        );
        layout.setVerticalGroup(
            layout.createParallelGroup(javax.swing.GroupLayout.Alignment.LEADING)
            .addComponent(jScrollPane1, javax.swing.GroupLayout.DEFAULT_SIZE, 250, Short.MAX_VALUE)
        );
    }// </editor-fold>//GEN-END:initComponents

    // Variables declaration - do not modify//GEN-BEGIN:variables
    private javax.swing.JScrollPane jScrollPane1;
    private javax.swing.JTable jTableParameters;
    // End of variables declaration//GEN-END:variables
    @Override
    public void componentOpened() {
        // TODO add custom code on component opening
    }

    @Override
    public void componentClosed() {

        String x = "10";
    }
    
    @Override
    protected String preferredID() {
        return "PLSQLParametersTopComponent" + ((String) ((_title == null) ? "" : _title));
        
    }
    
    @Override
    public boolean canClose() {
        return false;
    }
    
    public void persist() {
        getLast().toPreferences(_title);
    }

    private TableModel getTableModel() {
        ParameterTableModel model = new ParameterTableModel();
        return model;
    }
    
    private void setCellEditors() {
        ParameterTableModel model = (ParameterTableModel) jTableParameters.getModel();
        TableColumn column = jTableParameters.getColumnModel().getColumn(0);
        column.setCellEditor(new ParameterCellEditor(model, 0));
        column = jTableParameters.getColumnModel().getColumn(1);
        column.setCellEditor(new ParameterCellEditor(model, 1));
        
    }
    private String truncateTitle(final String title) {
        return title.substring(title.lastIndexOf(File.separatorChar) + 1, title.length());
    }
    
    private class ParameterTableModel extends DefaultTableModel implements ActionListener {
        
        boolean _dirty;
        
        private Class<?>[] _types = new Class<?>[] { Type.class, Type.class};
        
        private String[] _colNames = {Bundle.LBL_Name_Column(), Bundle.LBL_VALUE_Column()};
        
        Type[][] _data = {
                {new Parameter(1), new Value("")},
                {new Parameter(2), new Value("")}, 
                {new Parameter(3), new Value("")},
                {new Parameter(4), new Value("")}, 
                {new Parameter(5), new Value("")},
                {new Parameter(6), new Value("")},
                {new Parameter(7), new Value("")},
                {new Parameter(8), new Value("")},
                {new Parameter(9), new Value("")},
                {new Parameter("${url}", 10), new Value("")}
            };
        
        ComboBoxModel[][] _models = {{
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>()}, {
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>(),
            new DefaultComboBoxModel<Type>(), new DefaultComboBoxModel<Type>()}};
        
        ParameterTableModel() {
            setColumnIdentifiers(_colNames);
            //setDataVector(_data, _colNames);
        }
        
        public void init(Data data) {
            String[][] strings = data._data;
            for (int i= 0, j = 0; i < 10; i++, j = j + 2) {
                _data[i][0].setContent(strings[i][0]);
                _data[i][1].setContent(strings[i][1]);
                
                List<String> list = data._modelData.get(j);
                DefaultComboBoxModel<Type> box = (DefaultComboBoxModel<Type>) _models[0][i];
                for(String param: list) {
                    if (param.equals("")) {
                        continue;
                    }
                    box.addElement(new Parameter(param, i+1));
                }
                box.setSelectedItem(new Parameter(_data[i][0].getContent(), 0));
                
                list = data._modelData.get(j+1);
                box = (DefaultComboBoxModel<Type>) _models[1][i];
                for(String value: list) {
                    if (value.equals("")) {
                        continue;
                    }
                    box.addElement(new Value(value));
                }
                String def = _data[i][1].getContent();
                if (def.equals("")) {
                    continue;
                }
                box.setSelectedItem(new Value(def));
            }
            
        }
        @Override
        public int getRowCount() {
            return 10;
        }

        @Override
        public int getColumnCount() {
            return 2;
        }
        
        @Override
        public String getColumnName(final int col) {
            return _colNames[col];
        }
        
        @Override
        public Class<?> getColumnClass(final int col) {
            return _types[col];
        }
        
        public boolean isCellEditable(final int row, final int col) {
            return true;
        }

        @Override
        public Object getValueAt(final int rowIndex, final int columnIndex) {
            return _data[rowIndex][columnIndex];
        }

        @Override
        public void actionPerformed(final ActionEvent e) {
           if ("comboBoxEdited".equals(e.getActionCommand())) {
                ParameterCellEditor.TableComboBox box = (ParameterCellEditor.TableComboBox)e.getSource();
                DefaultComboBoxModel<Type> model = (DefaultComboBoxModel<Type>)box.getModel();
                Object selected = box.getSelectedItem();
                int i = model.getSize() - 1;
                for (; i >= 0; i--) {
                    if (selected.equals(model.getElementAt(i))) {
                        box.setSelectedItem(selected);
                        return;
                    }
                }
                if (selected.toString().equals("")) {
                    box.removeItem(box._last);
                    if (box.getItemCount() >= 1) {
                        box.setSelectedIndex(0);
                    } else {
                        box._model._data[box._row][box._col].setContent(null);
                        _dirty = true;
                    }
                    return;
                }
                //model.addElement(selected);
                selected = Type.promote(selected, box._row, box._col);
                box.addItem((Type) selected);
                box.setSelectedItem(selected);
            }
        }
    }
    
    static class ParameterCellEditor extends DefaultCellEditor {
        TableComboBox _box;
        ComboBoxModel<Type>[] _models;
        ParameterCellEditor(ParameterTableModel model, int col) {
            super(new TableComboBox(model, col));
            _box = (TableComboBox) this.getComponent();
            _box.addActionListener(model);
            _box.setEditable(true);
            _models = model._models[col];
        }
        
        @Override
        public Component getTableCellEditorComponent(final JTable table, final Object value, 
                final boolean isSelected, final int row, final int column)  {
            _box.setModel(_models[row]);
            _box.setRow(row);
            Component comp = super.getTableCellEditorComponent(table, value, isSelected, row, column);
            return comp;
            
        }
        static class TableComboBox extends JComboBox<Type> {

            private final ParameterTableModel _model;
            private final int _col;
            private int _row;
            private Type _last;
            public TableComboBox(final ParameterTableModel model, final int col) {
                _model = model;
                _col = col;
            }
            
            void setRow(final int row) {
                _row = row;
            }

            @Override
            public void setSelectedItem(final Object item) {
                final Type type = Type.promote(item, _row, _col);
                super.setSelectedItem(type);
                if (getSelectedIndex() == -1) {
                    return;
                }
                _last = type;
                _model._data[_row][_col].setContent(type.getContent());
                _model._dirty = true;
            }

        }
    
    }
    
    // for serialization of preferences
    private final static class Data implements Serializable {
        public static final long serialVersionUID = 1l;
        
        public String [][] _data = new String[10][2];
        public List<List<String>> _modelData;
        
        public static final Data DEFAULT = new Data();
        
        Data() {
            _data = new String[][] {
                {"&1", ""}, {"&2", ""}, {"&3", ""}, {"&4", ""}, {"&5", ""}, 
                {"&6", ""}, {"&7", ""}, {"&8", ""},{"&9", ""}, {"${url}", ""}
            };
            _modelData = new ArrayList<List<String>>(20);
            for (int i = 0; i < 10; i++) {
                List<String> param = new ArrayList<String>(2);
                param.add("&"+(i+1));
                _modelData.add(param);
                List<String> value = new ArrayList<String>(2);
                value.add("");
                _modelData.add(value);
            }
        }
        
        Data(final Type[][] types) {
            for (int i = 0; i < 10; i++) {
                for (int j = 0; j < 2; j++) {
                    _data[i][j] = types[i][j].getContent();
                }
            }
        }
        
        Data(final ParameterTableModel model) {
            this(model._data);
            _modelData = new ArrayList<List<String>>(20);
            for (int i = 0; i < 10; i++) {
                List<String> param = new ArrayList<String>(2);
                List<String> value = new ArrayList<String>(2);
                DefaultComboBoxModel<Type> box = (DefaultComboBoxModel<Type>) model._models[0][i];
                int size = box.getSize();
                for (int j = 0; j < size; j++) {
                    param.add(box.getElementAt(j).getContent());
                }
                _modelData.add(param);
                box = (DefaultComboBoxModel<Type>) model._models[1][i];
                size = box.getSize();
                for (int j = 0; j < size; j++) {
                    value.add(box.getElementAt(j).getContent());
                }
                _modelData.add(value);
            }
        }
        
        String [][] getActive() {
            return _data;
        }
        
        Data fromPerferences(final String key) {
            final Preferences perfs = NbPreferences.forModule(PLSQLParametersTopComponent.class);
            if (perfs == null) {
                return this;
            }
            byte [] serialized = perfs.getByteArray(key, null);
            if (serialized == null) {
                return this;
            }
            ByteArrayInputStream bais = new ByteArrayInputStream(serialized);
            ObjectInputStream ois = null;
            String[][] data = null;
            List<List<String>> modelData = null;
            try {
                ois = new ObjectInputStream(bais);
                data = (String[][]) ois.readObject();
                modelData = (List<List<String>>) ois.readObject();
                ois.close();
            } catch (IOException ex) {
                Exceptions.printStackTrace(ex);
            } catch (ClassNotFoundException ex) {
                Exceptions.printStackTrace(ex);
            } finally {
                if (ois != null) {
                    try {
                        ois.close();
                    } catch (IOException ex) {
                    }
                }
                if (data != null) {
                    _data = data;
                }
                if (modelData != null) {
                    _modelData = modelData;
                }
            }
            
            return this;
        }
        
        void toPreferences(final String key) {
            final Preferences perfs = NbPreferences.forModule(PLSQLParametersTopComponent.class);
            if (perfs == null) {
                return;
            }
            ByteArrayOutputStream baos = new ByteArrayOutputStream(1024);
            ObjectOutputStream oos = null;
            try {
                oos = new ObjectOutputStream(baos);
                oos.writeObject(_data);
                oos.writeObject(_modelData);
                oos.close();
                perfs.putByteArray(key, baos.toByteArray());
                perfs.flush();
            } catch (IOException ex) {
            } catch (BackingStoreException ex) {
            } finally {
                if (oos != null) {
                    try {
                        oos.close();
                    } catch (IOException ex) {
                    }
                }
            }
        }
        
        
    }
    
    public String[][] getData() {
        return getLast().getActive();
    }
    
    private Data getLast() {
        return new Data((ParameterTableModel) jTableParameters.getModel());
    }
    
    private abstract static class Type {
        private String _content;
        
        public Type(final String content) {
            _content = content;
        }
        
        public String getContent() {
            return _content;
        }
        
        public void setContent(final String content) {
            if (content == null) {
                _content = "";
                return;
            }
            _content = content;
        }
        
        @Override
        public boolean equals(final Object alien) {
            if (!(alien instanceof Type)) {
                return false;
            }
            if (_content.equals(((Type) alien)._content)) {
                return true;
            }
            return false;
            
        }

        @Override
        public int hashCode() {
            int hash = 7;
            hash = 29 * hash + (this._content != null ? this._content.hashCode() : 0);
            return hash;
        }
        
        @Override
        public String toString() {
            return _content;
        }
        
        public static Type promote(Object candidate, int row, int col) {
            if (candidate instanceof Type) {
                return (Type) candidate;
            }
            if (col == 0) {
                return new Parameter((String) candidate, row);
            }
            return new Value((String) candidate);
        }
    }
    
    private static class Parameter extends Type {
        private int _pos;
        public Parameter(final String content, final int row) {
            super(content);
            _pos = row + 1;
            if (_pos == 10) {
                setContent("${url}");
            }
        }
        
        public Parameter(int pos) {
            super("&" + pos);
            _pos = pos;
        }
        
        @Override
        public void setContent(String content) {
            if (content == null || content.length() == 0) {
                content = "&" + _pos;
            }
            super.setContent(content);
        }
    }
    
    private static class Value extends Type {
        public Value(final String content) {
            super(content);
        }
    }
    
    private static class TooltipTable extends JTable {
        public TooltipTable() {
            super();
        }
        public String getToolTipText(MouseEvent e) {
            String tip = null;
            java.awt.Point p = e.getPoint();
            int rowIndex = rowAtPoint(p);
            int colIndex = columnAtPoint(p);

            try {
              tip = getValueAt(rowIndex, colIndex).toString();
            } catch (RuntimeException e1) {
                //catch null pointer exception if mouse is over an empty line
            }

            return tip;
        }
    }
}
